'use strict'

const fs = require('fs')
const http = require('http')
const config = require('./config.json')
const utils = require('./util')
const { setInterval } = require('timers')


// 球球计数变量，用于设置球球ID
let ballCount = 1

// 球球基类
class Ball {
    constructor(x, y, color, mass) {
        this.x = x
        this.y = y
        this.color = color
        this.setMass(mass)

        this.boostX = 0
		this.boostY = 0
		this.boostMaxSpeed = 0

        this.id = ballCount++
    }

    // 设置质量的同时设置半径
    setMass(mass) {
        this.mass = mass
        this.radius = utils.massToRadius(mass)
    }

    addMass(mass) {
        this.setMass(this.mass + mass)
    }

    // 获取当前球球与目标坐标的角度
    getAngle(targetX, targetY) {
		return Math.atan2(targetY - this.y, targetX - this.x)
	}

    // 获取当前球球与目标坐标的距离
    getDistance(targetX, targetY) {
		return Math.hypot(targetX - this.x, targetY - this.y)
	}


    // 加速移动，boostXY为移动距离
	boost(deltaTime) {
        if(!this.isMove) {
            return;
        }
		this.x += this.boostX * deltaTime
		this.y += this.boostY * deltaTime
		let speed = Math.hypot(this.boostX, this.boostY) || 0.1
        if(speed < 1) {
            this.isMove = false
            return
        }
		this.boostX /= 1 + (16 / speed) * deltaTime + deltaTime
		this.boostY /= 1 + (16 / speed) * deltaTime + deltaTime
    }
	
	setBoost(boostX, boostY) {
        this.isMove = true
		this.boostX = boostX
		this.boostY = boostY
		this.boostMaxSpeed = Math.hypot(boostX, boostY)
	}

    // 检测当前球球与目标球球是否碰撞
    checkCellCollision(ball) {
		let dx = ball.x - this.x
		let dy = ball.y - this.y
		let distance = Math.hypot(dx, dy)
	
		if (distance < this.radius + ball.radius) {
			return {
				dx,
				dy,
				distance
			}
		}
	}
}

// 彩豆
class Food extends Ball {
    constructor(x, y) {
        super(x, y, utils.colorToString(utils.getRandomColor()), utils.getRandomRange(config.minFoodMass, config.maxFoodMass))
    }
}

// 孢子
class Spore extends Ball {
    constructor(x, y, color) {
        super(x, y, color, config.sporeMass)
    }
}

// 刺球
class Virus extends Ball {
    constructor(x, y) {
        super(x, y, '#22f63a', config.virusMass)
    }
}

// 分身
class Cell extends Ball {
    constructor(x, y, color, mass, owner) {
        super(x, y, color, mass)
        this.owner = owner
    }

    // 移动分身
    move(deltaTime) {
        let speed = 20 * this.radius ** -0.4
        let dx = Math.cos(this.angle) * speed
        let dy = Math.sin(this.angle) * speed
        this.x += dx * deltaTime
        this.y += dy * deltaTime
    }

    // 重写boost方法，因为分身的移动速度更快，衰减也更快
	boost(deltaTime) {
        if(!this.isMove) {
            return;
        }
		this.x += (this.boostX * 1.5) * deltaTime
		this.y += (this.boostY * 1.5) * deltaTime
		let speed = Math.hypot(this.boostX, this.boostY) || 0.1
        if(speed < 1) {
            this.isMove = false
            return
        }
		this.boostX /= 1 + (42 / speed) * deltaTime + deltaTime
		this.boostY /= 1 + (42 / speed) * deltaTime + deltaTime
    }
}


// 玩家
class Player {
    constructor(client) {
        this.client = client
    
        // 玩家状态：
        // 0：已连接但未开始，1：已连接且游戏中，2：死亡，3：掉线
        this.status = 0

        this.cells = new Set()
        this.viewBalls = new Set()

        this.lastVomitTime = 0
        this.lastSplitTime = 0

        this.isVomit = false
        this.isSplit = false

        this.mouseX = 0
        this.mouseY = 0

        // 视角相关
        this.viewScale = 0
        this.viewCenterX = 0
		this.viewCenterY = 0

        this.viewWidth = 200
        this.viewHeight = 200

		this.viewBox = {
			x: 0,
			y: 0,
			width: 0,
			height: 0
		}

        // 总质量
        this.totalMass = 0
    }

    // 玩家开始游戏
    start(name, color) {
        console.log(name + "\t" + color)
        this.name = name
        this.color = color

        this.status = 1 // 玩家正在游戏中

        // 设置玩家初始分身
        let cell = new Cell(
            Math.random() * config.mapWidth,
            Math.random() * config.mapHeight,
            color,
            config.playerMass,
            this
        )
        
        addBall(cell)
    }

    // 更新
    update() {
        // this.updateScale()
        this.updateCenter()
        // this.updateViewBox()
        

        // 计算总质量
        this.totalMass = 0
        this.cells.forEach((cell) => {
            if(!cell.isDelete) {
                this.totalMass += cell.mass
            }
        })

        // 将视野内的球球加入到viewBalls中
        this.viewBalls.clear()
        gameMap.balls.forEach((ball) => {

/*

            if(!ball.isDelete && utils.checkOverlap(
                {
                    x: this.viewBox.x,
                    y: this.viewBox.y
                },
                {
                    x: this.viewBox.x + this.viewBox.width,
                    y: this.viewBox.y + this.viewBox.height
                },
                {
                    x: ball.x,
                    y: ball.y
                },
                ball.radius
            )) {

            }

*/

            if(ball.getDistance(this.viewCenterX, this.viewCenterY) > 300) {
                return;
            }


            this.viewBalls.add({
                x: ball.x,
                y: ball.y,
                mass: ball.mass,
                color: ball.color,
                radius: ball.radius,
                type: ball.constructor.name,
                id: ball.id,
                name: ball.owner ? ball.owner.name : null
            })
        })
    }

    // 更新缩放
    updateScale() {
        // this.viewScale = 1

        // 自动设置视野缩放
		let cells = [...this.cells]
		const totalRadius = cells.map(cell => cell.radius).reduce((a, b) => a + b)
		let viewZoom = (totalRadius / (1 + cells.length / 12) * 4 + 112)
		this.viewScale = Math.max(this.viewHeight, this.viewWidth) / viewZoom
    }

	// 更新中心坐标
	updateCenter() {
		if(this.cells.size == 1) {
			// 如果只有一个分身，则使用这个分身作为视野中心坐标
			let cells = Array.from(this.cells)
			this.viewCenterX = cells[0].x
			this.viewCenterY = cells[0].y
			return
		}
		let avg = {
			x: 0,
			y: 0
		}
		Array.from(this.cells).forEach((cell) => {
			avg.x += cell.x
			avg.y += cell.y
		})
		avg.x /= this.cells.size
		avg.y /= this.cells.size
		this.viewCenterX = (this.viewCenterX * 2 + avg.x) / 3
		this.viewCenterY = (this.viewCenterY * 2 + avg.y) / 3
	}

    // 更新视区
    updateViewBox() {
        let scale = this.viewScale
        let width = this.viewWidth / scale
        let height = this.viewHeight / scale
        this.viewBox = {
            x: this.viewCenterX - width / 2,
            y: this.viewCenterY - height / 2,
            width,
            height
        }
    }

    // 吐球
	vomit(currentTime) {
        if(currentTime - this.lastVomitTime < 120) {
            return
        }
        this.lastVomitTime = currentTime
        this.cells.forEach((cell) => {
            if(cell.isDelete || cell.mass < 36) {
                // 如果该分身已被删除或者该分身的质量不足以吐球则不能吐球
                return;
            }
            cell.addMass(-config.sporeMass)
            let mx = Math.cos(cell.angle)
            let my = Math.sin(cell.angle)
            let spore = new Spore(
                cell.x + mx + mx * cell.radius,
                cell.y + my + my * cell.radius,
                cell.color
            )
            spore.setBoost(mx * 64, my * 64)
            addBall(spore)
        })
	}

    // 分身
    split(currentTime) {
        if (currentTime - this.lastSplitTime < 120 || this.cells.size >= 16) {
            return
        }
        this.lastSplitTime = currentTime
        let splitCount = 0
        Array.from(this.cells).map((cell) => {
            if (!cell.isDelete && cell.mass >= 36 && splitCount + this.cells.size < 16) {
                ++splitCount
                const angle = cell.getAngle(this.mouseX, this.mouseY)
                const mx = Math.cos(angle)
                const my = Math.sin(angle)
                cell.setMass(cell.mass * 0.5)
                let splitCell = new Cell(
                    cell.x + mx * cell.radius, 
                    cell.y + my * cell.radius, 
                    cell.color, 
                    cell.mass, 
                    this)
                // if(splitCell.radius > 60) {
                //     console.log(splitCell.radius)
                //     splitCell.setBoost(
                //         (mx * (cell.radius * 2)), 
                //         (my * (cell.radius * 2))
                //     )
                // } else {
                //     splitCell.setBoost(
                //         (mx * 60), 
                //         (my * 60)
                //     )
                // }
                splitCell.setBoost(
                    mx * (30 + (splitCell.radius * 1.5)),
                    my * (30 + (splitCell.radius * 1.5))
                )
                return splitCell
            } else {
                // return none
            }
        }).forEach((cell) => {
            if (cell) {
                addBall(cell)
            }
        })
    }
}


const gameMap = {
    width: config.mapWidth,
    height: config.mapHeight,
    balls: [],
    cacheBalls: [],
    players: []
}


let server = http.createServer(function (request, response) {

    // 防止跨域报错
    const headers = {

        "Access-Control-Allow-Headers": "Content-Type, Authorization",

        "Access-Control-Allow-Origin": request.headers.origin || "*", 

        "Access-Control-Allow-Methods": "PUT,GET,POST,DELETE,OPTIONS",
        "Access-Control-Allow-Credentials": true

    };
    response.writeHead(200, headers);
    if(request.url.startsWith('/client/')) {
        // console.log('.' + request.url)
        if(fs.existsSync('.' + request.url)) {
            response.end(fs.readFileSync('.' + request.url))
        } else {
            response.end()
        }
    } else {
        response.end()
    }
    // response.writeHead(200, { 'Content-Type': 'text/html' })
})

server.listen(config.port, config.host)

let socketio = require('socket.io')(server, { cors: true })

 

socketio.on('connection', function (client) {

    console.log('玩家连接...')

    let player = new Player(client)

    // 将该玩家添加到玩家列表中
    gameMap.players.push(player)

    client.on('start', function (data) {
        // 玩家开始游戏
        console.log('玩家开始游戏')
        console.log(data)
        player.start(
            data.name,
            utils.colorToString(utils.getRandomColor())
        )
        client.emit('started')
    })

    client.on('message', function (data) {
        // 玩家发送聊天数据，支持发送文字、表情、坐标
        console.log(data)
        socketio.sockets.emit('message', {
            name: player.name,
            color: player.color,
            msg: data.msg
        })
    })

    client.on('mousemove', function (data) {
        // 玩家鼠标移动
        if(player.status != 1) {
            return;
        }
        player.mouseX = data.x
        player.mouseY = data.y
    })

    client.on('vomit', function(data) {
        player.isVomit = data.vomit
    })

    client.on('split', function(data) {
        player.isSplit = data.split
    })

    client.on('disconnect', function() {
        console.log('玩家断开连接：' + player.name)
        player.status = 3
    })
})


// 预增加球球
function addBall(ball) {
    if(ball) {
        gameMap.cacheBalls.push(ball)
    }
}

// 增加预增加球球
function addAllBall() {
    gameMap.cacheBalls.forEach((ball) => {
        gameMap.balls.push(ball)
        if(ball.owner) {
            ball.owner.cells.add(ball)
        }
    })
    gameMap.cacheBalls.length = 0
}

// 删除预删除球球
function removeAllBall() {
    gameMap.balls = gameMap.balls.filter((ball) => {
        if(ball.isDelete) {
            if(ball.owner) {
                ball.owner.cells.delete(ball)
            }
            return false
        }
        return true
    })
}

function updateBalls(deltaTime) {

    let foodCount = 0
    let virusCount = 0

    gameMap.balls.forEach((ball) => {
        if(ball.isDelete) {
            // 该球球已被删除，不需要为其处理事件
            return;
        }
        
        if(ball.constructor == Food) {
            ++foodCount // 记录菜豆数量方便更新
        } else if(ball.constructor == Virus) {
            ++virusCount
        } else if(ball.constructor == Spore) {
            // 移动孢子
            ball.boost(deltaTime)
        } else if(ball.constructor == Cell) {
            // move player cell
            if(ball.owner && ball.owner.status == 1) {
                // 体重衰减
                // ball.setMass(ball.mass * 0.999998)
                // 更新球球角度
                ball.angle = ball.getAngle(ball.owner.mouseX, ball.owner.mouseY)
                ball.boost(deltaTime)
                ball.move(deltaTime)
            }
            
            gameMap.balls.forEach((target) => {
                if(target.isDelete) {
                    return;
                }

                if(ball == target) {
                    // 如果是同一个球球则不用处理
                    return;
                }

                // resolveRigidCollision
                if(ball.owner == target.owner) {
                    // 如果这两个球球属于同一个玩家的分身
                    let collision = ball.checkCellCollision(target)
                    if(collision) {
                        // 如果发生碰撞
                        let {
                            dx,
                            dy,
                            distance
                        } = collision
                        if(distance == 0) {
                            distance = 0.0001
                            dx += 0.0001
                        }
                        const push = (ball.radius + target.radius - distance) / distance
                        if(push == 0) {
                            return
                        }
                        const rt = ball.radius + target.radius
                        const r1 = push * ball.radius / rt
                        const r2 = push * target.radius / rt
                        
                        let v = Math.max((1 - Math.max(Math.hypot(ball.boostX, ball.boostY), 
                                Math.hypot(target.boostX, target.boostY)) / Math.max(ball.boostMaxSpeed,
                                    target.boostMaxSpeed)) * 2 - 0.6, 0)
                        // console.log(r1, r2, v)
                        ball.x -= dx * r2 * Math.min(deltaTime * 48, 1) * v
                        ball.y -= dy * r2 * Math.min(deltaTime * 48, 1) * v
                        target.x += dx * r1 * Math.min(deltaTime * 48, 1) * v
                        target.y += dy * r1 * Math.min(deltaTime * 48, 1) * v
                    }
                }

                // eats
                if(ball.owner != target.owner
                    && ball.mass * 0.8 > target.mass
                    && ball.getDistance(target.x, target.y) < ball.radius - target.radius * 0.35) {
                    target.isDelete = true
                    ball.addMass(target.mass)
                    if(target.owner && target.owner.cells.size == 1) {
                        // 如果对方就一个分身则给对方设置死亡状态
                        target.owner.status = 2
                    }
                    if(target.constructor == Virus) {
                        // 如果是绿刺
                        // 炸刺
                        // 最多分裂出9个分身
                        let angle = 0
                        let splitCount = 0
                        for(let i = 0; i < 9; i++) {
                            if(ball.mass <= 60 || ball.owner.cells.size + splitCount >= 16) {
                                break;
                            }
                            splitCount++;
                            let mx = Math.cos(angle)
                            let my = Math.sin(angle)
                            let newCell = new Cell(
                                ball.x + mx * ball.radius,
                                ball.y + my * ball.radius,
                                ball.color,
                                25,
                                ball.owner
                            )
                            newCell.setBoost(mx * 64, my * 64)
                            addBall(newCell)
                            
                            ball.addMass(-25)
                            
                            angle += 40
                        }
                    }
                }
            })
        }
    })

    // 刷新彩豆和绿刺
    for(let i = foodCount; i < config.foodAmount; i++) {
        addBall(new Food(Math.random() * config.mapWidth, Math.random() * config.mapHeight))
    }
    for(let i = virusCount; i < config.virusAmount; i++) {
        addBall(new Virus(Math.random() * config.mapWidth, Math.random() * config.mapHeight))
    }

    // 删除球球
    removeAllBall()

    // 增加球球
    addAllBall()
}

function updatePlayers() {

    // 更新玩家数据并发送
    // 因为不能在循环中删除元素，所以这里用一个临时的数组存储未删除的元素
    let tempPlayers = new Array(gameMap.players.length)
    gameMap.players.forEach((player) => {
        if(player.status == 3) {
            // 如果该玩家已掉线且分身都被吃掉了，就将该玩家删除
            // 删除该玩家的球球
            player.cells.forEach((ball) => {
                ball.isDelete = true
            })
            return;
        }
        if(player.status == 2) {
            // 如果该玩家已经死亡，则通知客户端
            player.client.emit('dead')
            player.status = 999 // 防止重复发送
        }
        if(player.client && player.status == 1) {
            player.update()
            if(player.isVomit) {
                player.vomit(Date.now())
            }
            if(player.isSplit) {
                player.split(Date.now())
                player.isSplit = false
            }   
            let data = {
                view: {
                    viewScale: player.viewScale,
                    viewCenterX: player.viewCenterX,
                    viewCenterY: player.viewCenterY,
                    viewBox: player.viewBox
                },
                balls: [...player.viewBalls],
                totalMass: player.totalMass
            }
            player.client.emit('heartbeat', data)
        }
        tempPlayers.push(player)
    })

    // 将临时数组赋值给players，完成删除元素的功能
    gameMap.players = tempPlayers
}

function gameloop(deltaTime) {
    updateBalls(deltaTime)
    updatePlayers()
}

function setup() {
    let lastTime = Date.now()
    // addBall(new Cell(
    //     100, 100, 'rgb(222, 122, 111)', 200, new Player(null)
    // ))
    setInterval(() => {
        let currentTime = Date.now()
        let elapsedSeconds = (currentTime - lastTime) / 1000
	    console.log(currentTime - lastTime)
	    lastTime = currentTime
        gameloop(elapsedSeconds)
    }, 1000 / config.timeStep)
}

function main() {
    setup()
}

main()
